/**
 * 
 */
package com.bloomingstars.sqm;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * @author Emmanuele Sordini
 *
 */
public class SqmChartPlotter {

    public static final int WIDTH = 640;
    public static final int HEIGHT = 480;
    public static final int ZERO_Y = HEIGHT - 85;
    public static final int ZERO_X = 85;
    public static final int MAX_X = WIDTH - 30;
    public static final int MAX_Y = 30;
    public static final double READING_RANGE = 17.0;
    public static final double MIN_READING = 6.0;
    
    private List<SqmReadingRecord> readings;
    private List<SelectedSqmReading> selectedReadings;
    private List<TimeHole> timeHoles = new ArrayList<TimeHole>();
    private double readingRes = Math.abs(MAX_Y - ZERO_Y) / READING_RANGE;
    private double timeRes = Math.abs(MAX_X - ZERO_X) / 57600.0;
    
    public SqmChartPlotter(List<SqmReadingRecord> readings) {
        this.readings = readings;
    }
    
    public void selectReadings() {        
        selectedReadings = new ArrayList<SelectedSqmReading>();
        
        TimeHole timeHole = new TimeHole(); 
        for (int i=0; i<readings.size(); i++) {     
            SqmReadingRecord record = readings.get(i);
            if (record.getSqmReading() > MIN_READING) {
                if (timeHole.index != -1) {
                    timeHole.end = record.getTime().getTimeInMillis();
                    timeHoles.add(timeHole);
                    timeHole = new TimeHole();
                }
                
                selectedReadings.add(new SelectedSqmReading(record, 0, 0));
            } else {
                if (timeHole.index == -1) {
                    timeHole.start = record.getTime().getTimeInMillis();
                    timeHole.index = i;                    
                }
            }
        }
    }
    
    public void calcReadingPositions() {
        double deltaT = 0.0;
        
        double elapsedSecsInHour = 0.0;
        if (selectedReadings.size() > 0) {
            double timeOfDay = selectedReadings.get(0).record.getTimeOfDay();
            elapsedSecsInHour = (timeOfDay - (int)timeOfDay) * 3600.0;                            
        }
        
        for (int i=0; i<selectedReadings.size(); i++) {
            SelectedSqmReading selReading = selectedReadings.get(i);
            if (i > 0) {
                 TimeHole hole = findTimeHole(i);
                 
                 if (hole == null) {
                     deltaT += (selectedReadings.get(i).record.getTime().getTimeInMillis() - 
                                selectedReadings.get(i-1).record.getTime().getTimeInMillis())/1000.0;
                 } else {
                     deltaT += 0.0;
                 }
            } else {
            }
            
            selReading.x = (int)(timeRes * (deltaT + elapsedSecsInHour)) + ZERO_X;
            System.out.println("Measure <" + i + ">: timeOfDay" + selReading.record.getTimeOfDay() + " - x = " +selReading.x);
            selReading.y = -(int)(readingRes*(selReading.record.getSqmReading()-MIN_READING)) + ZERO_Y; 
        }                
    }
    
    public BufferedImage renderChart() {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        
        Graphics2D g = image.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, image.getWidth() - 1, image.getHeight() - 1);
        
        g.setColor(Color.BLACK);
        g.drawLine(ZERO_X, ZERO_Y, ZERO_X, MAX_Y);
        g.drawLine(ZERO_X, ZERO_Y, MAX_X, ZERO_Y);
        
        plotYAxis(g);
        plotXAxis(g);
        plotLabels(g);
        
        g.setColor(Color.RED);
        for (SelectedSqmReading r: selectedReadings) {
            Polygon polygon = new Polygon();
            polygon.addPoint(r.x, r.y - 2);
            polygon.addPoint(r.x + 2, r.y + 2);
            polygon.addPoint(r.x - 2, r.y + 2);
          
            g.fillPolygon(polygon);
//           g.drawLine(r.x, r.y, r.x, r.y);
        }
        return image;
    }
    
    private void plotLabels(Graphics2D g) {
        g = (Graphics2D) g.create();
        
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        String s = "Live SQM reading in Besozzo (Italy) - Start date: ";
        String dt = "N/A";
        
        if (selectedReadings.size() > 0) {
            dt = format.format(selectedReadings.get(0).record.getTime().getTime());
            s += dt;
        }
        
        Font font = new Font( "Courier New", Font.PLAIN, 12);
        g.setColor(Color.BLACK);
        g.setFont(font);
        g.drawString(s, 100, 15);
        
        g.drawString("Time (local time: CET or CEST)" , 200, HEIGHT - 15);
        AffineTransform transform = new AffineTransform();
        transform.rotate(Math.toRadians(-90.0), 0, 0);        
        g.setColor(Color.BLACK);
        Font rotatedFont = font.deriveFont(transform);
        g.setFont(rotatedFont);
        g.drawString("Mags per square arcsecond" , 20, 300);                    
        
        g.dispose();
    }
    
    private TimeHole findTimeHole(int i) {
        for (TimeHole hole: timeHoles) {
            if (hole.index == i) {
                return hole;
            }
        }
        return null;
    }
    
    private void plotYAxis(Graphics2D g) {
        g = (Graphics2D) g.create();
        Stroke dashed = new BasicStroke(0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{2}, 0);        
        for (double tick=0; tick<=READING_RANGE; tick=tick+1.0) {
            int tickY = ZERO_Y - (int)(tick * this.readingRes);            
        
            g.drawLine(ZERO_X, tickY, ZERO_X-10, tickY);
            g.setStroke(dashed);
            g.drawLine(ZERO_X, tickY, MAX_X, tickY);
            g.setColor(Color.BLACK);
            g.setFont(new Font( "Courier New", Font.PLAIN, 12));
            g.drawString(String.format(Locale.US, "%5.2f", tick+MIN_READING), ZERO_X - 45, tickY+5);                    
        }
        g.dispose();
    }
    
    private void plotXAxis(Graphics2D g) {        
        timeRes = Math.abs(MAX_X - ZERO_X) / 57600.0;
        g = (Graphics2D) g.create();

        for (double tick = 0; tick<=57600.0; tick += 3600.0) {
            int tickX = (int)(timeRes * tick) + ZERO_X;
            g.drawLine(tickX,  ZERO_Y, tickX, ZERO_Y+10);            
        }
        
        List<GraphTickTime> tickTimes = calcTimeList();
        for (int tick=0; tick<16; tick++) {
            int tickX = (int)(timeRes * tick*3600.0) + ZERO_X;

            AffineTransform transform = new AffineTransform();
            transform.rotate(Math.toRadians(-90.0), 0, 0);
          
            Font font = new Font( "Courier New", Font.PLAIN, 12);
            g.setColor(Color.BLACK);
            Font rotatedFont = font.deriveFont(transform);
            g.setFont(rotatedFont);
            g.drawString(tickTimes.get(tick).hourString, tickX + 5, ZERO_Y + 50);            
        }
        g.dispose();
    }
    
    private List<GraphTickTime> calcTimeList() {
        List<GraphTickTime> times = new ArrayList<GraphTickTime>();
        
        if (selectedReadings.size() == 0) return times;
        
        int time = (int) selectedReadings.get(0).record.getTimeOfDay();
        times.add(new GraphTickTime(time, formatHourString(time)));
        for (SelectedSqmReading selectedReading: selectedReadings) {
            int tempTime = (int) selectedReading.record.getTimeOfDay();
            
            if (tempTime != time) {
                time = tempTime;
                times.add(new GraphTickTime(tempTime, formatHourString(time)));                
            }
        }
        
        if (times.size() < 16) {
            time = times.get(times.size() - 1).hour;
            for (int i=times.size(); i<16; i++) {
                time++;
                if (time>24) time -= 24;
                times.add(new GraphTickTime(time, formatHourString(time)));
            }
        }
        return times;
    }
    
    private String formatHourString(int hour) {
        String result = "ERROR";
        if (hour < 10) {
            result = "0" + Integer.toString(hour) + ":00";
        } else {
            result = Integer.toString(hour) + ":00";
        }
        return result;
    }
}

class SelectedSqmReading {        
    public SelectedSqmReading(SqmReadingRecord record, int x, int y) {
        super();
        this.record = record;
        this.x = x;
        this.y = y;
    }
    
    SqmReadingRecord record;    
    int x;
    int y;
}

class TimeHole {
    
    public TimeHole() {
        start = -1;
        index = -1;
        end = -1;
    }
    
    long start;
    long end;
    int index;
}

class GraphTickTime {
    int hour;
    String hourString;
    
    public GraphTickTime(int hour, String hourString) {
        this.hour = hour;
        this.hourString = hourString;
    }
    
    
}